﻿#coding=utf-8

from ctypes import *
from my_debugger_defines import *

kernel32 = windll.kernel32

class debugger():
	def __init__(self):  #初始化
		#pass
		self.h_process = None
		self.pid = None
		self.debugger_active = False
		self.h_thread = None
		self.context = None
		self.exception = None
		self.exception_address = None
		self.breakpoints = {}
		self.first_breakpoint = True
		self.hardware_breakpoints = {}
	
	def load(self,path_to_exe):
		#参数dwCreationFlags中的标志位控制着进程的创建方式。你若希望新创建的进程独占一个新的控制台窗口，而不是与父进程
		#共用同一个控制台，你可以加上标志位CREATE_NEW_CONSOLE creation_flags = DEBUG_PROCESS
		creation_flags  =   DEBUG_RPOCESS

		#实例化之前定义的结构体
		startupinfo     =   STARTUPINFO()
		process_information =   PROCESS_INFORMATION()

		#在以下两个成员变量的共同作用下，新建进程将在一个单独的窗体中被显示，你可以通过改变结构体STARTUPINFO中的各成员
		#变量的值来控制debugee进程的行为。
		startupinfo.dwFlags     =   0x1
		startupinfo.wShowWindow =   0x0

		#设置结构体STARTUPINFO中的成员变量
		#cb的值，用以表示结构体本身的大小
		startupinfo.cb = sizeof(startupinfo)

		if kernel32.CreateProcessA(path_to_exe,
									None,
									None,
									None,
									None,
									creation_flags,
									None,
									None,
									byref(startupinfo),
									byref(process_information)):
				
			print "[*] We have successfully lauched the process!"
			print "[*] PID: %d" % process_information.dwProcessId
            
            #保存一个指向新建进程的有效句柄,以供后续进程访问使用
			self.h_process = self.open_process(process_information.dwProcessId)
		else:    
			print "[*] Error with error code %d." % kernel32.GetLastError()
            
	def open_process(self,pid):
		#PROCESS_ALL_ACCESS对进程内存的全部操作权限
		#pid,若函数执行成功，将返回一个指向目标进程对象的句柄
		h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,False,pid)
		return h_process
	def attach(self,pid):
		self.h_process = self.open_process(pid)
		#试图附加到目标进程，若附加失败，则输出提示后返回
		if kernel32.DebugActiveProcess(pid):
			self.debugger_active = True
			self.pid = int(pid)
			#self.run()
		else:
			print "[*] Unable to attach to the process."
	def run(self):
		# Now we have to poll the debuggee for
        # debugging events
		#现在我们就来轮询调试调试事件
		while self.debugger_active == True:
			self.get_debug_event()   
           
	def get_debug_event(self):
		debug_event = DEBUG_EVENT()
		continue_status= DBG_CONTINUE
		
		if kernel32.WaitForDebugEvent(byref(debug_event),INFINITE):
			#获取相关线程的句柄并提取上下文环境信息
			self.h_thread = self.open_thread(debug_event.dwThreadID)
			self.context = self.get_thread_context(self.h_thread)
			
			print "Event Code: %d Thread ID: %d" % \
				(debug_event.dwDebugEventCode, debug_event.dwThreadID)
            # 目前我们还没有构建任何与事件处理相关的逻辑功能
            # 这里我们只是简单的恢复执行目标进程
			#raw_input("Press a key to continue...")
			#self.debugger_active = False
			
			#若时间代码显示这是一个异常事件则进一步检测其确切类型
			if debug_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT:
				#获取异常代码
				self.exception = debug_event.u.Exception.ExceptionRecord.ExceptionCode
				self.exception_address = debug_event.u.Exception.ExceptionRecord.ExceptionAddress
				
				if self.exception == EXCEPTION_ACCESS_VIOLATION:
					print "Access Violation Detected"
				#若检测到一个断点,则调用相应的处理例程
				elif self.exception == EXCEPTION_BREAKPOINT:
					continue_status = self.exception_handler_breakpoint()
				elif self.exception == EXCEPTION_GUARD_PAGE:
					print "Guard Page Access Detected"
				elif self.exception == EXCEPTION_SINGLE_STEP:
					self.exception_handler_single_step()
			
			kernel32.ContinueDebugEvent( \
				debug_event.dwProcessId, \
				debug_event.dwThreadID, \
				continue_status )
	def detach(self):
		if kernel32.DebugActiveProcessStop(self.pid):
			print "[*] Finished debugging. Exiting..."
			return True
		else:
			print "There was an error"
			return False 
			
	def open_thread(self, thread_id):  
		h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None, thread_id)
		#OpenThread()获取线程句柄
		#THREAD_ALL_ACCESS取得对线程的全部操作权限
		#thread_id线程id
		
		if h_thread is not None:
			return h_thread
		else:
			print "[*] Could not obtain a valid thread handle."
			return False
	
	def enumerate_thread(self): #枚举线程
		
		thread_entry = THREADENTRY32()  #初始化结构体
		thread_list = []  #空列表
		snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, self.pid)
		#用CreateToolhelp32Snapshot()进行线程枚举，可以得到系统中的线程列表，返回该列表的句柄值
		#TH32CS_SNAPTHREAD(0x00000004)表示我们要取得当前系统快照对象中的所有线程信息
		
		if snapshot is not None:
			#你需要正确的设置这个结构体的大小,否则会调用失败
			thread_entry.dwSize = sizeof(thread_entry)
			success = kernel32.Thread32First(snapshot, byref(thread_entry))
			#Thread32First()获取线程列表中的第一个线程
			#第一个参数表示系统中所有线程列表的句柄值
			#第二个参数是指向结构体THREADENTRY32()的指针
			
			while success:
				if thread_entry.th32OwnerProcessID == self.pid:
				#th32OwnerProcessID表示PID,可以得知当前枚举到的线程是运行在那一个进程下
				#比较结构体中的th32OwnerProcessID与该进程的PID值来判断该线程是否属于这个进程
					thread_list.append(thread_entry.th32ThreadID)
					#th32ThreadID表示当前被枚举到的线程的TID,得到相应的线程句柄
					#若属于同一个PID，则将它的TID写入列表中
					
				success = kernel32.Thread32Next(snapshot, byref(thread_entry))
				#Thread32Next获取线程列表中的下一个线程
				
			kernel32.CloseHandle(snapshot)
			#关闭事件句柄
			return thread_list 
			#返回整个TID句柄值列表
		else:
			return False
			
	def get_thread_context (self, thread_id=None, h_thread=None ):
		
		context = CONTEXT()
		context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
		
		#获取线程句柄
		
		h_thread = self.open_thread(thread_id)
		if kernel32.GetThreadContext(h_thread, byref(context)):
			kernel32.CloseHandle(h_thread)
			return context
		else:
			return False
	
	def read_process_memory(self, address, length):
		
		data = ""
		read_buf = create_string_buffer(length)
		count = c_ulong(0)
		
		if not kernel32.ReadProcessMemory(self.h_process, address, read_buf, length, byref(count)):
			return False
		else:
			data += read_buf.raw
			return data
			
	def write_process_memory(self, address, data):
		
		count = c_ulong(0)
		length = len(data)
		
		c_data = a_char_p(data[count.value:])
		
		if not kernel32.WriteProcessMemory(self.h_process, address, c_data, length, byref(count)):
			return False
		else:
			return True
			
	def bp_set(self, address):
		
		if not self.breakpoints.has_key(address):
			try:
				#备份这个内存地址上原有的字节值
				original_byte = self.read_process_memory(address, 1)
				#写入一个INT3中断指令,其操作码为0xCC
				self.write_process_memory(address, "\xCC")
				#将设下的断点记录在一个内部的断点列表中
				self.breakpoints[addres] = (address, original_byte)
			except:
				return False
		return True
			
	def exception_handler_breakpoint(self):
		print "[*] Inside the breakpoint handler"
		print "Exception address: 0x%08x" % self.exception_address
		return DBG_CONTINUE
		
	def func_resolve(self, dll, function):
		
		handle = kernel32.GetModuleHandleA(dll)
		address = kernel32.GetProcAddress(handle, function)
		
		kernel32.CloseHandle(handle)
		
		return address
		
	def bp_set_hw(self, address, length, condition):
		
		#检测硬件断点的长度是否有效
		if length not in (1, 2, 4):
			return False
		else:
			length -= 1
			
		#检测硬件断点的触发条件是否有效
		if condition not in (HW_ACCESS, HW_EXECUTE, HW_WRITE):
			return False
			
		#检测是否存在空置的调试寄存器槽
		if not self.hardware_breakpoints.has_key(0):
			available = 0
		elif not self.hardware_breakpoints.has_key(1):
			available = 1
		elif not self.hardware_breakpoints.has_key(2):
			available = 2
		elif not self.hardware_breakpoints.has_key(3):
			available = 3
		else:
			return False
			
		#我们要在每一个线程环境下设置调试寄存器
		for thread_id in self.enumerate_thread():
			context = self.get_thread_context(thread_id = thread_id)
			
			#通过设置DR7相应的标志位来激活断点
			context.Dr7 |= 1 << (available * 2)
		
			#在空置的寄存器中写入我们的断点
			if available == 0:
				context.Dr0 = address
			elif available == 1:
				context.Dr1 = address
			elif available == 2:
				context.Dr2 = address
			elif available == 3:
				context.Dr3 = address
			
			#设置硬件断点的触发条件
			context.Dr7 |= condition << ((available * 4) + 16)
		
			#设置硬件断点的长度
			context.Dr7 |= length << ((available * 4) + 18)
		
			#提交经过改动后的线程上下文环境信息
			h_thread = self.open_thread(thread_id)
			kernel32.SetThreadContext(h_thread, byref(context))
		
		#更新内部的硬件断点列表
		self.hardware_breakpoints[available] = (address, length, condition)
		
		return True
		
	def exception_handler_single_step(self):
		#print "[*] Exception address: 0x%08x" % self.exception_address
		#摘自PyDbg源码中的一段注释
		#判断这个单步事件是否由一个硬件断点所触发,若是则捕获这个断点
		#事件,根据Inter给出的文档,我们应当能够通过检测DR6寄存器上的BS标志
		#位来判断出这个单步事件的触发原因,然而Windows系统似乎并没有正确
		#的将这个标志位传递给我们
		if self.context.Dr6 & 0x1 and self.hardware_breakpoints.has_key(0):
			slot = 0
			
		elif self.context.Dr6 & 0x2 and self.hardware_breakpoints.has_key(1):
			slot = 1
		elif self.context.Dr6 & 0x4 and self.hardware_breakpoints.has_key(2):
			slot = 2
		elif self.context.Dr6 & 0x8 and self.hardware_breakpoints.has_key(3):
			slot = 3
		else:
			#这个INT1中断并非由一个硬件断点所引发
			continue_status = DBG_EXCEPTION_NOT_HANDLED
			
		#从断点列表移除这个断点
		if self.bp_del_hw(slot):
			continue_status = DBG_CONTINUE
			
		print ("[*] Hardware breakpoint removed.")
		return continue_status
		
	def bp_del_hw(self, slot):
		#为所有的执行线程移除断点
		for thread_id in self.enumerate_thread():
			context = self.get_thread_context(thread_id = thread_id)
			
			#通过重设标志位来移除这个硬件断点
			context.Dr7 &= ~(1 << (slot * 2))
			
			#将断点地址清零
			if slot == 0:
				context.Dr0 = 0x00000000
			elif slot ==1:
				context.Dr1 = 0x00000000
			elif slot ==2:
				context.Dr2 = 0x00000000
			elif slot ==3:
				context.Dr3 = 0x00000000
				
			#清空断点触发条件标志位
			context.Dr7 &= ~(3 << ((slot * 4) + 16))
			
			#情况断点长度标志位
			context.Dr7 &= ~(3 << ((slot * 4) + 18))
			
			#提交移除断点后的线程上下文信息
			h_thread = self.open_thread(thread_id)
			kernel32.SetThreadContext(h_thread, byref(context))
			
		#将这个断点从内部的断点列表移除
		del self.hardware_breakpoints[slot]
		return True
	
			
			